﻿//  Copyright (c) 2011 Ray Liang (http://www.dotnetage.com)
//  Licensed MIT: http://www.opensource.org/licenses/mit-license.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DNA.Mvc.Publishing.Data.Entity
{
    public class CategoryRepository : GenericPubEntityRepository<Category>, ICategoryRepository
    {
        public CategoryRepository() : base() { }

        public CategoryRepository(PubDB db) : base(db) { }

        public IQueryable<Category> Descendants(int categoryID)
        {
            var cat = Find(categoryID);
            return DbSet.Where(c => c.Path.StartsWith(cat.Path.ToLower()) && c.ID != categoryID);
        }

        public IQueryable<Category> Children(int categoryID)
        {
            return DbSet.Where(c => c.ParentID == categoryID);
        }

        public Category Find(string path)
        {
            return DbSet.FirstOrDefault(c => c.Path.Equals(path.ToLower()));
        }

        public IQueryable<Category> Includes(int[] ids)
        {
            return DbSet.Where(c => ids.Contains(c.ID));
        }

        public IQueryable<Archive> GetArchives(string website)
        {
            var cats = DbSet.Where(c => c.Path.StartsWith(website));
            var catKeys = cats.Select(c => c.ID);
            var comparer = new ArchiveComparer();
            var _archives = Context.Set<Article>().Where(a => catKeys.Contains(a.CategoryID))
                                                                   .ToList()
                                                                   .Select(a => new Archive()
                                                                   {
                                                                       Year = a.Posted.Year,
                                                                       Month = a.Posted.Month
                                                                   })
                                                                    .Distinct(comparer);
            return _archives.AsQueryable();
        }

        public int GetChildrenCount(int categoryID)
        {
            return Count(c => c.ParentID == categoryID);
        }

        private void UpdateDescendantsPath(int id, string path, string targetPath)
        {
            var descendants = DbSet.Where(c => c.Path.StartsWith(path) && c.ID != id);
            foreach (var child in descendants)
                child.Path=child.Path.Replace(path, targetPath);
        }

        public void Move(int parentID, int id, int pos)
        {
            var category = Find(id);
            var parentCategory = Find(parentID);

            if (category == null)
                throw new Exception(string.Format("Category {0} not found", id));

            var pathParts = category.Path.Split('/');
            var curPath = parentCategory.Path + "/" + pathParts[pathParts.Length - 1];

            var seqCollection = DbSet.Where(p => p.ParentID == parentID)
                                                .OrderBy(p => p.Pos)
                                                .Select(p => p.Pos);

            if (seqCollection.Count() == 0)
            {
                category.Pos = 0;

                if (category.ParentID != parentID)
                {
                    UpdateDescendantsPath(id,category.Path, curPath);
                    category.Path = curPath;
                    category.ParentID = parentID;
                }

                if (IsOwnContext)
                    Context.SaveChanges();
                return;
            }

            int upperBound = seqCollection.Max();
            int lowerBound = seqCollection.Min();

            int _from = category.Pos;
            int _to = pos;

            if (_to > upperBound)
                _to = upperBound;
            else
            {
                if (_to < lowerBound)
                    _to = lowerBound;
            }

            //1.Move up
            if (_from > _to)
            {
                DbSet.Where(p => p.ParentID == parentID && p.Pos >= _to && p.Pos < _from)
                    .OrderBy(p => pos)
                    .AsParallel()
                    .ForAll(p => p.Pos++);

            }

            //2.Move down
            if (_from < _to)
            {
                DbSet.Where(p => p.ParentID == parentID && p.Pos > _from && p.Pos <= _to)
                   .OrderBy(p => pos)
                   .AsParallel()
                   .ForAll(p => p.Pos--);
            }

            category.Pos = _to;

            if (category.ParentID != parentID)
            {
                UpdateDescendantsPath(id,category.Path, curPath);
                category.Path = curPath;
                category.ParentID = parentID;
            }

            if (IsOwnContext) Context.SaveChanges();
        }

        public IQueryable<Tag> GetTags(string website, out int count)
        {
            var cats = DbSet.Where(c => c.Path.StartsWith(website));
            var catKeys = cats.Select(c => c.ID);
            var tagStr = Context.Set<Article>().Where(a => (!string.IsNullOrEmpty(a.Tags) && catKeys.Contains(a.CategoryID)))
                                                .Select(a => a.Tags)
                                                .ToArray();
            string fullTags = "";
            if (tagStr.Length > 0)
                fullTags = string.Join(",", tagStr);
            var tagArgs = fullTags.Split(new char[] { ',' });
            var tagList = new List<Tag>();

            foreach (var tag in tagArgs)
            {
                var _tag = tagList.FirstOrDefault(t => t.Name.Equals(tag, StringComparison.OrdinalIgnoreCase));
                if (_tag == null)
                    tagList.Add(new Tag() { Name = tag, Count = 1 });
                else
                    _tag.Count++;
            }
            count = tagStr.Length;
            return tagList.AsQueryable();
        }

        public int GetDescendantArticleCount(int categoryID)
        {
            var descendantIDs = Descendants(categoryID).Select(c=>c.ID).ToList();
            
            if (!descendantIDs.Contains(categoryID))
                descendantIDs.Add(categoryID);

            if (descendantIDs != null && descendantIDs.Count() > 0)
                return Context.Articles.Count(a => descendantIDs.Contains(a.CategoryID));

            return 0;
        }
    }
}
